/**
 * <p>
 *   将任意对象转换为FormData类型，用于multipart/form-data类型的请求。
 * </p>
 * <p>
 *   为什么要写这个工具呢，因为某些时候将普通表单项和文件表单项同放到一个请求中发送，
 *   如果手动将表单项一个个append()进FormData的话非常繁琐，所以就封装了这个工具类
 * </p>
 * <pre>
 * multipart/form-data类型请求，springBoot如何自动转换数据
 *
 * 普通类型（对应springBoot参数对象中的单个属性）：
 * meetingCategory: 1
 * name: aaa
 *
 * 数组类型（对应springBoot参数对象中的 List<类型> participantList 类型的属性）：
 * participantList[0].memberId: 123
 * participantList[0].isJoin: 0
 * participantList[1].memberId: 666
 * participantList[1].isJoin: 1
 *
 * 数组也可以是普通类型
 * receiverIdList[2]: 4
 * receiverIdList[3]: 5
 * receiverIdList[4]: 7
 *
 * 深层对象会这样解释
 * js对象：{
 *       info: {
 *         name: 'aaa',
 *         participantList: [
 *           { memberId: 123, isJoin: 0 },
 *           { memberId: 666, isJoin: 1 },
 *         ],
 *       },
 *       ext: {
 *         createUser: 'tom',
 *         updateUser: 'jerry'
 *         readUserIdList: [1, 2, 3]
 *       }
 *     }
 * 会被解释为：
 * info.name: aaa
 * info.participantList[0].memberId: 123
 * info.participantList[0].isJoin: 0
 * info.participantList[1].memberId: 666
 * info.participantList[1].isJoin: 1
 * ext.createUser: tom
 * ext.updateUser: jerry
 * ext.readUserIdList[0]: 1
 * ext.readUserIdList[1]: 2
 * ext.readUserIdList[2]: 3
 *
 * 举例1：
 * const formData = FormDataUtil.toFormData(原始对象)
 * formData.append('name', '这是一个名字')
 *
 * 举例2：
 * const formData = new FormData()
 * FormDataUtil.appendObjectFlat(formData, 原始对象)
 * formData.append('name', '这是一个名字')
 *
 * </pre>
 *
 * @author 郭泳辰
 * @date 2024年5月22日
 */
export const FormDataUtil = {
  /**
   * <pre>
   * 将对象扁平化开，并根据属性名放入formData中，支持深度递归
   * 注意：支持的成员类型：string、number、boolean、Date、Array、File
   *       不支持的成员类型被忽略
   *       对象必须是{}大括号类型的对象，数组类型必须是[]方括号类型的数组
   *       不支持直接将一个非对象传入，例如appendObjectFlat(formData, 'tom', 'name')，请使用原始的formData.append('name', 'tom')
   * </pre>
   *
   * @param formData FormData类型对象
   * @param obj 对象
   * @param prefixName 放入formData中的名称前缀（首次调用可忽略）
   * @author 郭泳辰
   * @date 2024年5月22日
   */
  appendObjectFlat: (formData: any, obj: any, prefixName?: string) => {
    for (const key in obj) {
      const value = obj[key] // 字段值
      const name: string = prefixName ? prefixName + key : key // 字段在formData中的名称
      if (value instanceof Array && value.length) {
        // 数组类型调用数组方法继续递归
        FormDataUtil.appendList(formData, value, name)
      } else if (FormDataUtil.canImmediatelyPutToFormData(value)) {
        // 将可以直接放到formData的类型放入formData（终止条件）
        formData.append(name, value)
      } else if (value instanceof Object) {
        // 其他对象类型继续递归
        FormDataUtil.appendObjectFlat(formData, value, `${name}.`)
      }
    }
  },

  /**
   * <pre>
   * 将数组转换为FormData类型
   * 注意：支持的数组类型：普通{}对象、string、number、boolean、Date、Array、File类型数组
   *        不支持的类型由appendObjectFlat进行处理
   * </pre>
   *
   * @param formData FormData类型对象
   * @param list 数组
   * @param name 数组在formData中的名称
   * @author 郭泳辰
   * @date 2024年5月22日
   */
  appendList: (formData: any, list: any[], name: string) => {
    list.forEach((item, index) => {
      if (FormDataUtil.canImmediatelyPutToFormData(item)) {
        // obj不是一个自定义对象，而是可以直接放到formData的类型，则直接放入formData（终止条件）
        formData.append(`${name}[${index}]`, item)
      } else {
        FormDataUtil.appendObjectFlat(formData, item, `${name}[${index}].`)
      }
    })
  },

  /**
   * <pre>
   *    判断是否为可以直接放入formData的类型，如果是返回true，否则返回false
   * </pre>
   * @param obj 任意对象
   * @return 如果是返回true，否则返回false
   * @author 郭泳辰
   * @date 2024年6月14日
   */
  canImmediatelyPutToFormData: (obj: any): boolean => {
    return (
      typeof obj === 'string' ||
      typeof obj === 'number' ||
      typeof obj === 'boolean' ||
      obj instanceof Date ||
      obj instanceof File
    )
  },

  /**
   * <pre>
   * 将对象转换为FormData类型，支持深度递归
   * 注意：支持的成员类型：string、number、boolean、Date、Array、File
   *       不支持的成员类型被忽略
   *       对象必须是{}大括号类型的对象，数组类型必须是[]方括号类型的数组
   * </pre>
   *
   * @param obj 对象
   * @returns 转换后的FormData类型对象
   * @author 郭泳辰
   * @date 2024年6月11日
   */
  toFormData: (obj: any): FormData => {
    const formData = new FormData()
    FormDataUtil.appendObjectFlat(formData, obj)
    return formData
  },
}
